package org.xteam.sled.semantic;

import java.util.Stack;

import org.xteam.parser.runtime.Span;
import org.xteam.sled.ast.AddExpr;
import org.xteam.sled.ast.BitRange;
import org.xteam.sled.ast.DefaultSledVisitor;
import org.xteam.sled.ast.Expr;
import org.xteam.sled.ast.ExtendExpr;
import org.xteam.sled.ast.IdentExpr;
import org.xteam.sled.ast.IntegerExpr;
import org.xteam.sled.ast.MulExpr;
import org.xteam.sled.semantic.exp.Exp;
import org.xteam.sled.semantic.exp.ExpAdd;
import org.xteam.sled.semantic.exp.ExpConst;
import org.xteam.sled.semantic.exp.ExpMul;
import org.xteam.sled.semantic.exp.ExpNarrow;
import org.xteam.sled.semantic.exp.ExpSlice;
import org.xteam.sled.semantic.exp.ExpVar;
import org.xteam.sled.semantic.exp.ExpWiden;


public class IntegerSemantic extends DefaultSledVisitor {
	
	// elabInt (A.APP _)               = error "constructor application"
	// elabInt (A.LITERAL_exp s)       = error "string literal"
	// app (A.OR, es)     = error "expected integer expression; found pattern"
	// app (A.AND, es)    = error "expected integer expression; found pattern"
	// app (A.CONCAT, es) = error "expected integer expression; found pattern"
	// app (A.PREFIX_DOTS,  _) = error "expected integer expression; found pattern"
	// app (A.POSTFIX_DOTS, _) = error "expected integer expression; found pattern"
	// app (A.SOME_token, es) = error "expected integer expression; found pattern"
	// app (A.PLABEL l, es)   = error "expected integer expression; found pattern"
	// app (A.RELOP rel, _) = error "expected integer expression; found condition or constraint"
   
	// app (A.MINUS, es as [_]) = unop'  Exp.neg    es
	// app (A.PLUS,  es       ) = binop' Exp.add   es
	// app (A.DIV,   es       ) = binop' divide es
	// app (A.WILDCARD, es) = OK (SINGLE (Exp.var (Wildcard.new())))
	
	private Stack<Exp> stack = new Stack<Exp>();
	private IEvaluationContext context;
	
	public IntegerSemantic(IEvaluationContext context) {
		this.context = context;
	}

	public Exp analyse(Expr expr) {
		expr.visit(this);
		return pop();
	}
	
	private Exp pop() {
		return stack.pop();
	}
	
	private void push(Exp exp) {
		stack.push(exp);
	}
	
	// elabInt (A.IDENT id)            = emap SINGLE (lookupInt id)
	public void visitIdentExpr(IdentExpr identExpr) {
		IDenotable denotable = context.lookup(identExpr.getName(), identExpr.span());
		push(denotable.projectInt(context, identExpr.span()));
	}
	
	// elabInt (A.INT n)               = OK (SINGLE (Exp.const n))
	public void visitIntegerExpr(IntegerExpr integerExpr) {
		push(new ExpConst(integerExpr.getValue().intValue()));
	}
	
	// app (A.MINUS, es       ) = binop' Exp.sub  es
	public void visitAddExpr(AddExpr addExpr) {
		addExpr.getLeft().visit(this);
		Exp l = pop();
		addExpr.getRight().visit(this);
		Exp r = pop();
		push(new ExpAdd(l, r));
	}
	
	// app (A.TIMES, es       ) = binop' times  es
	public void visitMulExpr(MulExpr mulExpr) {
		mulExpr.getLeft().visit(this);
		Exp l = pop();
		mulExpr.getRight().visit(this);
		Exp r = pop();
		if (l.isConst())
			push(new ExpMul(((ExpConst)l).value(), r));
		else if (r.isConst())
			push(new ExpMul(((ExpConst)r).value(), l));
		else
			context.reportError(mulExpr.span(), "multiplication by non-constant");
	}
	
	// app (A.EXTEND, es)   = strip(emap (groupErrors o gmap (unapp smartWiden)) (elabInts es))
	public void visitExtendExpr(ExtendExpr extendExpr) {
		extendExpr.getExpr().visit(this);
		Exp e = pop();
		if (e instanceof ExpSlice) {
			ExpSlice slice = (ExpSlice) e;
			push(new ExpWiden(e, slice.width()));
		} else if (e instanceof ExpNarrow) {
			ExpNarrow narrow = (ExpNarrow) e;
			push(new ExpWiden(e, narrow.width()));
		} else if (e instanceof ExpVar) {
			ExpVar var = (ExpVar) e;
			Span span = extendExpr.span();
			IDenotable denotable = context.lookupGlobal(var.name(), span);
			if (denotable instanceof Field) {
				push(new ExpWiden(e, ((Field)denotable).size()));
			} else
				context.reportError(span, "can only widen fields, slices, and narrows, not " + var.name());
		} else {
			context.reportError(extendExpr.span(), "can only widen fields, slices, and narrows, not " + e);
		}
	}
	
	// app (A.SLICE, es)    = gemap slice (elabInts es)
	public void visitBitRange(BitRange bitRange) {
		bitRange.getIdent().visit(this);
		int low = bitRange.getLow().getValue();
		int high = bitRange.getHigh() != null ? bitRange.getHigh().getValue() : low;
		push(new ExpSlice(pop(), low, high + 1 - low));
	}
	
}
